/*
 * Copyright (c) 2011 Vasile Vilvoiu. All rights reserved.
 *   
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * cmgr.c - layer over the sockets API that standardizes connections
 */
#include "cmgr.h"
#include "ferror.h"
#include <malloc.h>

#define MAX_CONNECTIONS			128
#define MAX_CHILD_CONNECTIONS	128

static CMGR_SOCKETS_INIT cmgr_sockets_init;
static CMGR_CONNECTION * cmgr_connections[MAX_CONNECTIONS] = { NULL };
static int cmgr_connection_count = 0;

/*
 * cmgr_create_connection - create a clean connection object
 *  returns: error code
 *	 connection(out): connection object created from parameters
 *
 *   NOTE: this function will allocate the memory itself and will write the object pointer to the
 *		   specified memory location. It should be called any time you create a new connection
 */
static int cmgr_create_connection(CMGR_CONNECTION **connection) {

	/* check listener */
	if (connection == NULL) {
		return err_error(ERR_BAD_PTR, "listener pointer is null", __HERE__);
	}

	/* allocate memory */
	*connection = (CMGR_CONNECTION *) malloc(sizeof(CMGR_CONNECTION));
	if (*connection == NULL) {
		return err_error(ERR_OUT_OF_MEM, "could not allocate new listener object", __HERE__);
	}

	/* populate object */
	(*connection)->port = 0;
	(*connection)->type = CONN_UNKNOWN;
	(*connection)->socket = CMGR_NULL_SOCKET;
	(*connection)->entry = NULL;
	(*connection)->thread = NULL;
	(*connection)->status = ST_READY;

	/* all ok */
	return ERR_NONE;
}

/*
 * cmgr_open_connection - open a connection
 *  returns: error code
 *   connection(in): connection to be opened
 */
static int cmgr_open_connection(CMGR_CONNECTION *connection) {
	
	int err = 0;
#if defined _WIN32 || defined _WIN64
	SOCKADDR_IN sockinit;
#endif


	/* check parameter */
	if (connection == NULL) {
		return err_error(ERR_BAD_PTR, "trying to open null connection", __HERE__);
	}

	/* if connection type is client connection, no socket initialisation is required */
	if (connection->type == CONN_CLIENT) {
		return ERR_NONE;
	}

#if defined _WIN32 || defined _WIN64

	/* open sock */
	connection->socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (connection->socket == INVALID_SOCKET) {
		err = WSAGetLastError();
		return err_errorf(ERR_SOCKET_CREATE_FAIL, "error code %d", __HERE__, err);
	}

	/* bind it */
	sockinit.sin_port = htons(connection->port);
	sockinit.sin_addr.s_addr = INADDR_ANY;
	sockinit.sin_family = AF_INET;
	err = bind(connection->socket, (SOCKADDR *) &sockinit, sizeof(sockinit));
	if (err == SOCKET_ERROR) {
		err = WSAGetLastError();
		return err_errorf(ERR_SOCKET_BIND_FAIL, "error code %d", __HERE__, err);
	}

#endif

#ifdef __linux__
#error not ported yet
#endif

	/* all ok */
	return ERR_NONE;
}

/*
 * cmgr_close_connection - close a connection
 *  returns: error code
 *   connection(in): connection to be closed
 */
static int cmgr_close_connection(CMGR_CONNECTION *connection) {
	
	int err = 0;

	/* update status flag */
	connection->status = ST_CLOSED;

	/* check parameter */
	if (connection == NULL) {
		return err_error(ERR_BAD_PTR, "trying to close null connection", __HERE__);
	}

#if defined _WIN32 || defined _WIN64
	/* close socket if necessarry */
	if (closesocket(connection->socket) == SOCKET_ERROR) {
		err = WSAGetLastError();
		return err_errorf(ERR_SOCKET_CLOSE_FAIL, "error code %d", __HERE__, err);
	}
#endif

#ifdef __linux__
#error not ported yet
#endif

	/* all ok */
	return ERR_NONE;
}

/*
 * cmgr_listen - listen for foreign connections on socket
 *  returns: error code
 *   connection(in): connection to set as listener
 */
static int cmgr_listen(CMGR_CONNECTION *connection) {

	int err = 0;

	/* check parameter */
	if (connection == NULL) {
		return err_error(ERR_BAD_PTR, "null connection specified for connection listener", __HERE__);
	}

#if defined _WIN32 || defined _WIN64

	if (listen(connection->socket, MAX_CHILD_CONNECTIONS) == SOCKET_ERROR) {
		err = WSAGetLastError();
		return err_errorf(ERR_SOCKET_LISTEN_FAIL, "error code %d", __HERE__, err);
	}

#endif

#ifdef __linux__
#endif

	/* all ok */
	return ERR_NONE;
}

/*
 * cmgr_accept - accept foreign connection
 *  returns: error code
 *   connection(in): connection that is listening
 *   client_sock(out): client connection
 */
static int cmgr_accept(CMGR_CONNECTION *connection, CMGR_SOCKET *client_sock) {

	int err = 0;
	CMGR_SOCKET client = CMGR_NULL_SOCKET;

	/* check parameter */
	if (connection == NULL) {
		return err_error(ERR_BAD_PTR, "null connection specified for connection listener", __HERE__);
	}

#if defined _WIN32 || defined _WIN64

	/* accept connection */
	client = accept(connection->socket, NULL, NULL);
	if (client == INVALID_SOCKET) {
		err = WSAGetLastError();
		return err_errorf(ERR_SOCKET_ACCEPT_FAIL, "error code %d", __HERE__, err);
	}

	/* return connection */
	*client_sock = client;

#endif

#ifdef __linux__
#endif

	/* all ok */
	return ERR_NONE;
}

/*
 * cmgr_listen_for_connections - generic connection listener
 *  returns: error code
 *   conn(in): connection struct that holds info about connection
 */
static THREAD_ENTRY_RETURN cmgr_listen_for_connections(THREAD_ENTRY_PARAM conn) {

	int err = 0;
	CMGR_CONNECTION *connection = NULL;
	CMGR_CONNECTION *client_conn = NULL;
	CMGR_SOCKET client_sock = CMGR_NULL_SOCKET;

	/* check param */
	if (conn == NULL) {
		return err_error(ERR_BAD_PTR, "null connection received by connection listener", __HERE__);
	}
	
	/* cast param as connection structure */
	connection = (CMGR_CONNECTION *) conn;

	/* mark as client connection listener */
	err = cmgr_listen(connection);
	if (err != ERR_NONE) {
		return err;
	}

	/* listen for connections */
	while (connection->status == ST_OPEN) {

		/* accept new connection */
		err = cmgr_accept(connection, &client_sock);

		/* if socket is ok, register the connection */
		if (cmgr_create_connection(&client_conn) == ERR_NONE) {
			client_conn->entry = connection->entry;
			client_conn->socket = client_sock;
			client_conn->status = ST_READY;
			client_conn->type = CONN_CLIENT;

			/* register connection */
			if (cmgr_register_connection(client_conn) != ERR_NONE) {
				/* connection could not be registered; free memory here and carry on */
				free(client_conn);
			}
		}
	}

	/* all ok */
	return ERR_NONE;
}

/*
 * cmgr_init - initialise the connection manager
 *  returns: error code
 */
int cmgr_init() {

	int err_code = ERR_NONE;
	int i = 0;

#if defined _WIN32 || defined _WIN64
	/* initialise winsock */
	err_code = WSAStartup(MAKEWORD(2,2), &cmgr_sockets_init);
	if (err_code != 0) {
		return err_errorf(ERR_WINSOCK_INIT_FAIL, "error code %d", __HERE__, err_code);
	}
#endif

#ifdef __linux__
#error not implemented yet
#endif

	/* deinitialise stuff (if needed) */
	for (i = 0; i < cmgr_connection_count; ++ i) {

		if (cmgr_connections[i]->status == ST_OPEN) {
			cmgr_close_connection(cmgr_connections[i]);
		}

		free(cmgr_connections[i]);
		cmgr_connections[i] = NULL;
	}

	cmgr_connection_count = 0;

	/* all ok */
	return ERR_NONE;
}

/*
 * cmgr_kill - deinitialise the connection manager
 *  returns: error code
 */
int cmgr_kill() {

#if defined _WIN32 || defined _WIN64
	/* winsock deinitialisation */
	WSACleanup();
#endif

#ifdef __linux__
#error not implemented yet
#endif

	/* nothing to do */
	return ERR_NONE;
}

/*
 * cmgr_create_listener - create a listener object to pass to connection manager
 *  returns: error code
 *   port(in): port to listen for connections
 *   entry(in): function to call when client connects
 *	 listener(out): connection object created from parameters
 *
 *   NOTE: this function will allocate the memory itself and will write the object pointer to the
 *		   specified memory location
 */
int cmgr_create_listener(int port, THREAD_ENTRY entry, CMGR_CONNECTION **listener) {

	int err = 0;

	/* check entry */
	if (entry == NULL) {
		return err_error(ERR_BAD_PTR, "entry function pointer is null", __HERE__);
	}

	/* check port */
	if (port < 1 || port > MAX_PORT) {
		return err_errorf(ERR_SOCKET_INVALID_PORT, "specified port (%d) is not in [1-65535] range", __HERE__, port);
	}

	/* create a cleared connection object */
	err = cmgr_create_connection(listener);
	if (err != ERR_NONE) {
		return err;
	}

	/* populate object */
	(*listener)->type = CONN_LISTENER;
	(*listener)->port = port;
	(*listener)->entry = entry;

	/* all ok */
	return ERR_NONE;
}

/*
 * cmgr_register_connection - add connection to connection list
 *  returns: error code
 *   connection(in): connection to register in the manager
 */
int cmgr_register_connection(CMGR_CONNECTION *connection) {

	/* check pointer */
	if (connection == NULL) {
		return err_error(ERR_BAD_PTR, "invalid connection pointer", __HERE__);
	}

	/* check capacity */
	if (cmgr_connection_count >= MAX_CONNECTIONS) {
		return err_error(ERR_MAX_CONNECTIONS, "", __HERE__);
	}

	/* add new connection to list */
	cmgr_connections[cmgr_connection_count] = connection;
	++ cmgr_connection_count;

	/* all ok */
	return ERR_NONE;
}

/*
 * cmgr_process_connections - process the connection list
 *  returns: error code
 *  
 *  NOTE: this function should be called constantly (in a loop) to clean up old connections and
 *		  initialise new ones
 */
int cmgr_handle_connections() {

	int index = 0;
	int err = 0;

	/* delete closed connections */
	while (index < cmgr_connection_count) {

		/* check connection status */
		if (cmgr_connections[index]->status == ST_CLOSED) {
			int i = 0;

			/* make sure connection is properly closed () */
			cmgr_close_connection(cmgr_connections[index]);

			/* free memory and shift whole list */
			free(cmgr_connections[index]);
			for (i = index; i < cmgr_connection_count-1; ++ i) {
				cmgr_connections[i] = cmgr_connections[i + 1];
			}
		}

		/* advance */
		++ index;
	}

	/* open new registered connections */
	for (index = 0; index < cmgr_connection_count; ++ index) {

		/* check connection status */
		if (cmgr_connections[index]->status == ST_READY) {

			/* open connection */
			if (cmgr_open_connection(cmgr_connections[index]) == ERR_NONE) {

				/* create thread */
				switch (cmgr_connections[index]->type) {
					case CONN_LISTENER:
						thread_create(cmgr_listen_for_connections, cmgr_connections[index], &cmgr_connections[index]->thread);
						break;

					case CONN_CLIENT:
					case CONN_OUTGOING:
						thread_create(cmgr_connections[index]->entry, cmgr_connections[index], &cmgr_connections[index]->thread);
						break;

					default:
						/* no type ??? WTF */
						err_error(ERR_BAD_PTR, "bad type specified for connection", __HERE__);
						cmgr_connections[index]->status = ST_CLOSED;
						break;
				}

				/* if thread could not be created, kill connection entirely; cleanup will be done in
				   the next call to cmgr_handle_connections() */
				if (cmgr_connections[index]->thread != NULL) {
					cmgr_connections[index]->status = ST_OPEN;
				} else {
					cmgr_connections[index]->status = ST_CLOSED;
				}
			} else {
				/* connection open failed, mark it as closed */
				cmgr_connections[index]->status = ST_CLOSED;
			}
		}
	}

	/* done, let other threads do stuff */
	thread_yield();

	/* all ok */
	return ERR_NONE;
}